package com.zf.common.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

@Configuration
@AutoConfigureBefore({RedisAutoConfiguration.class})
public class RedisConfig {


    /**
     * 配置 RedisTemplate，使用Json方式序列化对象
     *
     * @param connectionFactory redis 连接工厂
     *                          1、当项目引入了 `redisson-spring-boot-starter`时，会创建一个 {@link org.redisson.spring.data.connection.RedissonConnectionFactory} 类型的 RedisConnectionFactory bean
     *                          此时配置的 lettuce 连接池相关信息就无效了；
     *                          2、如果只引入了 `spring-boot-starter-data-redis`时，会创建一个 {@link org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory} 类型的  RedisConnectionFactory bean；
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        // Json转换器：从对象转json和从json转对象
        ObjectMapper objectMapper = new ObjectMapper();
        // 指定要序列化的域(field,get,set)，访问修饰符，any表示全部修饰符
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 不序列化 final 修饰的类型
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);

        // 禁止把时间类型（Date,Calendar）序列化成时间戳，这样设置后（Date,Calendar）会序列化成“yyyy-MM-ddTHH:mm:ss.SSS”类型，
        // 解决jackson无法将时间戳反序列化为LocalDateTime的问题
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        // 创建 JavaTimeModule ，其内部有一些默认的时间类型（jsr310中的时间类型，也就是jdk8中新增的时间类型）的序列化和反序列格式
        JavaTimeModule timeModule = new JavaTimeModule();
        // 指定 LocalDate、LocalDateTime、LocalTime 序列化和反序列化格式，如果不指定将按照默认格式
        timeModule.addDeserializer(LocalDate.class,
                new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addDeserializer(LocalDateTime.class,
                new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addDeserializer(LocalTime.class,
                new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        timeModule.addSerializer(LocalDate.class,
                new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addSerializer(LocalDateTime.class,
                new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addSerializer(LocalTime.class,
                new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));

        // 必须要注册 JavaTimeModule，如果不指定格式，会使用默认格式。
        // 如果不注册JavaTimeModule，那么LocalDateTime、LocalDate、LocalTime等jdk8中新的时间类型序列化会成功，但是反序列化会失败
        objectMapper.registerModule(timeModule);

        // 用 StringRedisSerializer 序列化和反序列化 redis中的key和hashKey
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // 用Jackson2JsonRedisSerializer来序列化和反序列化redis中的value值
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        // 指定序列化和反序列化的配置
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // key, hashKey, value, hashValue 的序列化方式
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
